﻿/* 

jTable 1.4
http://www.jtable.org

---------------------------------------------------------------------------

Copyright (C) 2011 by Halil İbrahim Kalkan (http://www.halilibrahimkalkan.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

/************************************************************************
* CORE jTable module                                                    *
*************************************************************************/
(function ($) {

    $.widget("hik.jtable", {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {

            actions: {},
            fields: {},

            defaultDateFormat: 'yy-mm-dd',
            dialogShowEffect: 'fade',
            dialogHideEffect: 'fade',

            showCloseButton: false,
            closeRequested: function (event, data) { },

            formCreated: function (event, data) { },
            formSubmitting: function (event, data) { },
            formClosed: function (event, data) { },

            loadingRecords: function (event, data) { },
            recordsLoaded: function (event, data) { },

            rowInserted: function (event, data) { },
            rowsRemoved: function (event, data) { },

            messages: {
                serverCommunicationError: 'An error occured while communicating to the server.',
                loadingMessage: 'Loading records...',
                noDataAvailable: 'No data available!',
                areYouSure: 'Are you sure?',
                save: 'Save',
                saving: 'Saving',
                cancel: 'Cancel',
                error: 'Error',
                close: 'Close',
                cannotLoadOptionsFor: 'Can not load options for field {0}'
            }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$mainContainer: null, //Reference to the main container of all elements that are created by this plug-in

        _$table: null, //Reference to the main <table> jQuery object
        _$tableBody: null, //Reference to <body> jQuery object in the table
        _$tableRows: null, //Array of all <tr> jQuery objects in the table (except "no data" row)

        _$bottomPanel: null, //Reference to the panel at the bottom of the table

        _$busyDiv: null, //Reference to the div that is used to block UI while busy.
        _$busyMessageDiv: null, //Reference to the div that is used to show some message when UI is blocked.
        _$errorDialogDiv: null, //Reference to the error dialog div

        _columnList: null, //Name of all columns in the table (except the last command column)
        _fieldList: null, //Name of all fields of a record in the table,
        _keyField: null, //Name of the key field of a record in the table

        _firstDataColumnOffset: 0, //Start index of first record field in columns (some columns can be placed before first data column)
        _lastPostData: {}, //Last posted data on load method.

        _cache: null, //General purpose cache dictionary

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Creates empty table.
        *************************************************************************/
        _create: function () {
            this._initializeFields();
            this._normalizeFieldsOptions();
            this._createFieldAndColumnList();

            this._createMainContainer();
            this._createTableTitle();
            this._createTable();
            this._createBottomPanel();
            this._createBusyPanel();

            this._createErrorDialogDiv();

            this._addNoDataRow();
        },

        _initializeFields: function () {
            this._$tableRows = [];
            this._columnList = [];
            this._fieldList = [];
            this._cache = [];
        },

        /* Normalizes some options for all fields (sets default values).
        *************************************************************************/
        _normalizeFieldsOptions: function () {
            var self = this;
            $.each(self.options.fields, function (fieldName, props) {
                self._normalizeFieldOptions(fieldName, props);
            });
        },

        /* Normalizes some options for a field (sets default values).
        *************************************************************************/
        _normalizeFieldOptions: function (fieldName, props) {
            props.listClass = props.listClass || '';
            props.inputClass = props.inputClass || '';
        },

        /* Fills _fieldList, _columnList and _keyField variables.
        *************************************************************************/
        _createFieldAndColumnList: function () {
            var self = this;

            $.each(self.options.fields, function (name, props) {

                //Add field to the field list
                self._fieldList.push(name);

                //Check if tihs field is the key field
                if (props.key == true) {
                    self._keyField = name;
                }

                //Add field to column list if it is shown in the table
                if (props.list != false) {
                    self._columnList.push(name);
                }
            });
        },

        /* Creates the main container.
        *************************************************************************/
        _createMainContainer: function () {
            var self = this;
            self._$mainContainer = $('<div class="jtable-main-container"></div>').appendTo(self.element);
        },

        /* Creates title of the table if a title supplied in options.
        *************************************************************************/
        _createTableTitle: function () {
            var self = this;
            if (!self.options.title) {
                return;
            }

            var $titleDiv = $('<div class="jtable-title"><div class="jtable-title-text">' + self.options.title + '</div></div>').appendTo(self._$mainContainer);

            if (self.options.showCloseButton) {
                $('<button class="jtable-command-button jtable-close-button" title="' + self.options.messages.close + '"><span>' + self.options.messages.close + '</span></button>')
                    .appendTo($titleDiv)
                    .click(function (e) {
                        e.preventDefault();
                        e.stopPropagation();
                        self._onCloseRequested();
                    });
            }
        },

        /* Creates table tag and adds to page.
        *************************************************************************/
        _createTable: function () {
            var self = this;
            self._$table = $('<table class="jtable"></table>').appendTo(self._$mainContainer);
            self._createTableHead();
            self._createTableBody();
        },

        /* Creates header (all column headers) of the table.
        *************************************************************************/
        _createTableHead: function () {
            var self = this;

            //Create header
            var $th = $('<thead></thead>').appendTo(self._$table);
            self._addRowToTableHead($th);
        },

        /* Adds tr element to given thead element
        *************************************************************************/
        _addRowToTableHead: function ($thead) {
            var self = this;
            var $tr = $('<tr></tr>').appendTo($thead);
            self._addColumnsToHeaderRow($tr);
        },

        /* Adds column header cells to given tr element.
        *************************************************************************/
        _addColumnsToHeaderRow: function ($tr) {
            var self = this;

            //Add column headers
            for (var i = 0; i < self._columnList.length; i++) {
                var fieldName = self._columnList[i];
                var $headerCell = self._createHeaderCellForField(fieldName, self.options.fields[fieldName]);
                $headerCell.data('fieldName', fieldName).appendTo($tr);
            }
        },

        /* Creates a header cell for given field.
        *************************************************************************/
        _createHeaderCellForField: function (fieldName, field) {
            return $('<th class="jtable-column-header" style="width:' + (field.width || '10%') +
                '"><div class="jtable-column-header-container"><span class="jtable-column-header-text">' + field.title +
                '</span></div></th>')
                .data('fieldName', fieldName);
        },

        /* Creates an empty header cell
        *************************************************************************/
        _createEmptyHeader: function () {
            return $('<th class="jtable-command-column-header"></th>');
        },

        /* Creates table tag and adds to page.
        *************************************************************************/
        _createTableBody: function () {
            var self = this;
            self._$tableBody = $('<tbody></tbody>').appendTo(self._$table);
        },

        /* Creates bottom panel and adds to page.
        *************************************************************************/
        _createBottomPanel: function () {
            var self = this;
            self._$bottomPanel = $('<div class="jtable-bottom-panel"></div>').appendTo(self._$mainContainer);
        },

        /* Creates and prepares error dialog div.
        *************************************************************************/
        _createErrorDialogDiv: function () {
            var self = this;

            self._$errorDialogDiv = $('<div></div>').appendTo(self._$mainContainer);
            self._$errorDialogDiv.dialog({
                autoOpen: false,
                show: self.options.dialogShowEffect,
                hide: self.options.dialogHideEffect,
                modal: true,
                title: self.options.messages.error,
                buttons: [{
                    text: self.options.messages.close,
                    click: function () {
                        self._$errorDialogDiv.dialog('close');
                    }
                }]
            });
        },

        /* Creates a div to blosk UI while busy.
        *************************************************************************/
        _createBusyPanel: function () {
            var self = this;
            self._$busyMessageDiv = $('<div class="jtable-busy-message"></div>').prependTo(self._$mainContainer);
            self._$busyDiv = $('<div class="jtable-busy-panel-background"></div>').prependTo(self._$mainContainer);
            self._hideBusy();
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        /* Loads data by AJAX call, clears table and fills with new data.
        *************************************************************************/
        load: function (postData, completeCallback) {
            this._lastPostData = postData;
            this._reloadTable(completeCallback);
        },

        /* Refreshes (re-loads) table data
        *************************************************************************/
        reload: function (completeCallback) {
            this._reloadTable(completeCallback);
        },

        /* Completely removes the table from it's container.
        *************************************************************************/
        destroy: function () {
            this.element.empty();
            $.Widget.prototype.destroy.call(this);
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* LOADING RECORDS  *****************************************************/

        _reloadTable: function (completeCallback) {
            var self = this;

            //Disable table since it's busy
            self._showBusy(self.options.messages.loadingMessage);

            //Generate URL (with query string parameters) to load records
            var loadUrl = self._createRecordLoadUrl();

            self._onLoadingRecords();
            $.ajax({
                url: loadUrl,
                type: 'POST',
                dataType: 'json',
                data: self._lastPostData,
                success: function (data) {

                    //Not busy anymore
                    self._hideBusy();

                    //Show the error message if server returns error
                    if (data.Result != 'OK') {
                        self._showError(data.Message);
                        return;
                    }

                    //Re-generate table rows
                    self._removeAllRows('reloading');
                    self._addRecordsToTable(data.Records);

                    //Trigger recordsLoaded event
                    self._onRecordsLoaded(data);

                    //Call complete callback
                    if (completeCallback) {
                        completeCallback();
                    }
                },
                error: function () {
                    self._hideBusy();
                    self._showError(self.options.messages.serverCommunicationError);
                }
            });
        },

        /* Creates URL to load records.
        *************************************************************************/
        _createRecordLoadUrl: function () {
            return this.options.actions.listAction;
        },

        /* TABLE MANIPULATION METHODS *******************************************/

        /* Creates a row from given record
        *************************************************************************/
        _createRowFromRecord: function (record) {
            var self = this;

            //create a new tr (table row) object
            var $newTableRow = $('<tr></tr>').data('record', record);

            self._addCellsToRowUsingRecord($newTableRow);

            return $newTableRow;
        },

        /* Adds cells to given row using given record.
        *************************************************************************/
        _addCellsToRowUsingRecord: function ($row) {
            var self = this;

            //Create cells
            var record = $row.data('record');
            for (var i = 0; i < self._columnList.length; i++) {
                //create new table cell for data field
                var $column = $('<td class="' + self.options.fields[self._columnList[i]].listClass + '"></td>').appendTo($row);
                $column.append((self._getDisplayTextForRecordField(record, self._columnList[i]) || ''));
            }
        },

        /* Adds a list of records to the table
        *************************************************************************/
        _addRecordsToTable: function (records) {
            var self = this;

            $.each(records, function (index, record) {
                self._addRowToTable(self._createRowFromRecord(record));
            });

            self._refreshRowStyles();
        },

        /* Adds a single row to a specific index of the table.
        *  If no index specified, adds to end of the table.
        *************************************************************************/
        _addRowToTable: function ($tableRow, index, isNewRow) {
            var self = this;

            if (isNewRow != true) {
                isNewRow = false;
            }

            if (self._$tableRows.length <= 0) {
                self._removeNoDataRow();
            }

            //Add new row to the table according to it's index
            index = self._normalizeNumber(index, 0, self._$tableRows.length, self._$tableRows.length);
            if (index == self._$tableRows.length) {
                //add as last row
                self._$tableBody.append($tableRow);
                self._$tableRows.push($tableRow);
            } else if (index == 0) {
                //add as first row
                self._$tableBody.prepend($tableRow);
                self._$tableRows.unshift($tableRow);
            } else {
                //insert to specified index
                self._$tableRows[index - 1].after($tableRow);
                self._$tableRows.splice(index, 0, $tableRow);
            }

            //Raise rowInserted event
            self._onRowInserted($tableRow, isNewRow);

            //Show animation if needed
            if (isNewRow == true) {
                self._refreshRowStyles();
                self._showNewRowAnimation($tableRow);
            }
        },

        /* Shows created animation for a table row
        *************************************************************************/
        _showNewRowAnimation: function ($tableRow) {
            $tableRow.addClass('jtable-row-created', 'slow', '', function () {
                $tableRow.removeClass('jtable-row-created', 5000);
            });
        },

        /* Removes a row from table.
        *************************************************************************/
        _removeRowsFromTable: function ($rows, reason) {
            var self = this;

            if ($rows.length <= 0) {
                return;
            }

            //remove from DOM
            $rows.remove();

            //remove from _$tableRows array
            $rows.each(function () {
                self._$tableRows.splice(self._findRowIndex($(this)), 1);
            });

            self._onRowsRemoved($rows, reason);

            if (self._$tableRows.length == 0) {
                self._addNoDataRow();
            }

            self._refreshRowStyles();
        },

        _findRowIndex: function ($row) {
            return this._findIndexInArray($row, this._$tableRows, function ($row1, $row2) {
                return $row1.data('record') == $row2.data('record');
            });
        },

        /* Removes all rows in the table and adds 'no data' row.
        *************************************************************************/
        _removeAllRows: function (reason) {
            var self = this;

            if (self._$tableRows.length <= 0) {
                return;
            }

            var $rows = self._$tableBody.find('tr');

            self._$tableBody.empty();
            self._$tableRows = [];

            self._onRowsRemoved($rows, reason);

            self._addNoDataRow();
        },

        /* Adds "no data available" row to the table
        *************************************************************************/
        _addNoDataRow: function () {
            var self = this;
            var totalColumnCount = self._$table.find('thead th').length;
            $('<tr class="jtable-no-data-row"></tr>')
                .append('<td colspan="' + totalColumnCount + '">' + self.options.messages.noDataAvailable + '</td>')
                .appendTo(self._$tableBody);
        },

        /* Removes "no data available" row to the table
        *************************************************************************/
        _removeNoDataRow: function () {
            this._$tableBody.find('.jtable-no-data-row').remove();
        },

        /* Refreshes style of all rows in the table
        *************************************************************************/
        _refreshRowStyles: function () {
            var self = this;
            var $tableRows = self._$tableRows;

            for (var i = 0; i < $tableRows.length; i++) {
                if (i % 2 == 0) {
                    $tableRows[i].addClass('jtable-row-even');
                } else {
                    $tableRows[i].removeClass('jtable-row-even');
                }
            }
        },

        /* RENDERING FIELD VALUES ***********************************************/

        /* Gets text for a field of a record according to it's type.
        *************************************************************************/
        _getDisplayTextForRecordField: function (record, fieldName) {
            var self = this;

            var field = self.options.fields[fieldName];
            var fieldValue = record[fieldName];

            if (field.display) {
                return field.display({ record: record });
            }

            if (field.type == 'date') {
                return self._getDisplayTextForDateRecordField(field, fieldValue);
            } else if (field.type == 'checkbox') {
                return self._getCheckBoxTextForFieldByValue(fieldName, fieldValue);
            } else if (field.options) {
                return self._getOptionsWithCaching(fieldName)[fieldValue];
            } else {
                return fieldValue;
            }
        },

        /* Gets text for a date field.
        *************************************************************************/
        _getDisplayTextForDateRecordField: function (field, fieldValue) {
            var self = this;

            var displayFormat = field.displayFormat || self.options.defaultDateFormat;
            var date = new Date(parseInt(fieldValue.substr(6)));
            return $.datepicker.formatDate(displayFormat, date);
        },

        /* ERROR DIALOG *********************************************************/

        /* Shows error message dialog with given message.
        *************************************************************************/
        _showError: function (message) {
            var self = this;
            self._$errorDialogDiv.html(message).dialog('open');
        },

        /* BUSY PANEL ***********************************************************/

        /* Shows busy indicator and blocks table UI.
        *************************************************************************/
        _showBusy: function (message) {
            var self = this;

            if (!self._$busyMessageDiv.is(':visible')) {
                self._$busyDiv.width(self._$mainContainer.width());
                self._$busyDiv.height(self._$mainContainer.height());
                self._$busyDiv.show();
                self._$busyMessageDiv.show();
            }

            self._$busyMessageDiv.html(message);
        },

        /* Hides busy indicator and unblocks table UI.
        *************************************************************************/
        _hideBusy: function () {
            this._$busyDiv.hide();
            this._$busyMessageDiv.html('').hide();
        },

        /* Returns true if jTable is busy.
        *************************************************************************/
        _isBusy: function () {
            return this._$busyMessageDiv.is(':visible');
        },

        /************************************************************************
        * EVENT RAISING METHODS                                                 *
        *************************************************************************/

        _onLoadingRecords: function () {
            this._trigger("loadingRecords", null, {});
        },

        _onRecordsLoaded: function (data) {
            this._trigger("recordsLoaded", null, { records: data.Records, serverResponse: data });
        },

        _onRowInserted: function ($row, isNewRow) {
            this._trigger("rowInserted", null, { row: $row, record: $row.data('record'), isNewRow: isNewRow });
        },

        _onRowsRemoved: function ($rows, reason) {
            this._trigger("rowsRemoved", null, { rows: $rows, reason: reason });
        },

        _onCloseRequested: function () {
            this._trigger("closeRequested", null, {});
        }

    });

} (jQuery));
/************************************************************************
* Some UTULITY methods used by jTable                                   *
*************************************************************************/
(function ($) {

    /* General-purpose utulity methods.
    *************************************************************************/
    $.extend(true, $.hik.jtable.prototype, {
        
        /* Gets property value of an object recursively
        *************************************************************************/
        _getPropertyOfObject: function(obj, propName) {
            if (propName.indexOf('.') < 0) {
                return obj[propName];
            } else {
                var preDot = propName.substring(0, propName.indexOf('.'));
                var postDot = propName.substring(propName.indexOf('.') + 1);
                return this._getPropertyOfObject(obj[preDot], postDot);
            }
        },

        /* Sets property value of an object recursively
        *************************************************************************/
        _setPropertyOfObject: function(obj, propName, value) {
            var self = this;

            if (propName.indexOf('.') < 0) {
                obj[propName] = value;
            } else {
                var preDot = propName.substring(0, propName.indexOf('.'));
                var postDot = propName.substring(propName.indexOf('.') + 1);
                self._setPropertyOfObject(obj[preDot], postDot, value);
            }
        },

        /* Inserts a value to an array if it does not exists in the array.
        *************************************************************************/
        _insertToArrayIfDoesNotExists: function(array, value) {
            if ($.inArray(value, array) < 0) {
                array.push(value);
            }
        },

        /* Finds index of an element in an array according to given comparision function
        *************************************************************************/
        _findIndexInArray: function(value, array, compareFunc) {

            //If not defined, use default comparision
            if (!compareFunc) {
                compareFunc = function(a, b) {
                    return a == b;
                };
            }

            for (var i = 0; i < array.length; i++) {
                if (compareFunc(value, array[i])) {
                    return i;
                }
            }

            return -1;
        },

        /* Normalizes a number between given bounds or sets to a defaultValue
        *  if it is undefined
        *************************************************************************/
        _normalizeNumber: function(number, min, max, defaultValue) {
            if (number == undefined || number == null) {
                return defaultValue;
            }

            if (number < min) {
                return min;
            }

            if (number > max) {
                return max;
            }

            return number;
        },

        /* Formats a string just like string.format in c#.
        *  Example:
        *  _formatString('Hello {0}','Halil') = 'Hello Halil'
        *************************************************************************/
        _formatString: function() {
            if (arguments.length == 0) {
                return null;
            }

            var str = arguments[0];
            for (var i = 1; i < arguments.length; i++) {
                var placeHolder = '{' + (i - 1) + '}';
                str = str.replace(placeHolder, arguments[i]);
            }

            return str;
        }

    });

})(jQuery);
/************************************************************************
* FORMS extension for jTable (base for edit/create forms)               *
*************************************************************************/
(function ($) {

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Creates label for an input element.
        *************************************************************************/
        _createInputLabelForRecordField: function (fieldName) {
            var self = this;
            return $('<div class="jtable-input-label">' + self.options.fields[fieldName].title + '</div>');
        },

        //Creates a hidden input element with given name and value
        _createInputForHidden: function (name, value) {
            return $('<input type="hidden" value="' + value + '" name="' + name + '"></input>');
        },

        /* Creates an input element according to field type.
        *************************************************************************/
        _createInputForRecordField: function (fieldName, value, record) {
            var self = this;
            var field = self.options.fields[fieldName];

            //If value if undefined, use defaultValue
            if (value == undefined) {
                value = field.defaultValue;
            }

            //Use custom function if supplied
            if (field.input) {
                var $input = $(field.input({ value: value, record: record }));
                if (!$input.attr('id')) {
                    $input.attr('id', 'Edit-' + fieldName);
                }
                
                return $input;
            }

            if (field.type == 'date') {
                return self._createDateInputForField(field, fieldName, value);
            } else if (field.type == 'textarea') {
                return self._createTextAreaForField(field, fieldName, value);
            } else if (field.type == 'password') {
                return self._createPasswordInputForField(field, fieldName, value);
            } else if (field.type == 'checkbox') {
                return self._createCheckboxForField(field, fieldName, value);
            } else if (field.options) {
                if (field.type == 'radiobutton') {
                    return self._createRadioButtonListForField(field, fieldName, value);
                } else {
                    return self._createDropDownListForField(field, fieldName, value);
                }
            } else {
                return self._createTextInputForField(field, fieldName, value);
            }
        },

        /* Creates a date input for a field.
        *************************************************************************/
        _createDateInputForField: function (field, fieldName, value) {
            var self = this;

            var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input>');
            var displayFormat = field.displayFormat || self.options.defaultDateFormat;
            $input.datepicker({ dateFormat: displayFormat });
            return $('<div class="jtable-input jtable-date-input"></div>').append($input);
        },

        /* Creates a standart textbox for a field.
        *************************************************************************/
        _createTextAreaForField: function (field, fieldName, value) {
            return $('<div class="jtable-input jtable-textarea-input"><textarea class="' + field.inputClass + '" id="Edit-' + fieldName + '" name="' + fieldName + '">' + (value || '') + '</textarea></div>');
        },

        /* Creates a standart textbox for a field.
        *************************************************************************/
        _createTextInputForField: function (field, fieldName, value) {
            return $('<div class="jtable-input jtable-text-input"><input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input></div>');
        },

        /* Creates a password input for a field.
        *************************************************************************/
        _createPasswordInputForField: function (field, fieldName, value) {
            return $('<div class="jtable-input jtable-password-input"><input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="password"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input></div>');
        },

        /* Creates a checkboxfor a field.
        *************************************************************************/
        _createCheckboxForField: function (field, fieldName, value) {
            var self = this;

            //If value is undefined, get unchecked state's value
            if (value == undefined) {
                value = value || self._getCheckBoxPropertiesForFieldByState(fieldName, false).Value;
            }

            //Create a container div
            var $containerDiv = $('<div class="jtable-input jtable-checkbox-input"></div>');

            //Create checkbox and check if needed
            var $checkBox = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="checkbox" name="' + fieldName + '" value="' + value + '" />').appendTo($containerDiv);

            //Create display text of checkbox for current state
            var $textSpan = $('<span>' + (field.formText || self._getCheckBoxTextForFieldByValue(fieldName, value)) + '</span>').appendTo($containerDiv);

            //Check the checkbox if it's value is checked-value
            if (self._getIsCheckBoxSelectedForFieldByValue(fieldName, value)) {
                $checkBox.attr('checked', 'checked');
            }

            //This method sets checkbox's value and text according to state of the checkbox
            var refreshCheckBoxValueAndText = function () {
                var checkboxProps = self._getCheckBoxPropertiesForFieldByState(fieldName, $checkBox.is(':checked'));
                $checkBox.attr('value', checkboxProps.Value);
                $textSpan.html(field.formText || checkboxProps.DisplayText);
            };

            //Register to click event to change display text when state of checkbox is changed.
            $checkBox.click(function () {
                refreshCheckBoxValueAndText();
            });

            //Change checkbox state when clicked to text
            if (field.setOnTextClick != false) {
                $textSpan.addClass('jtable-option-text-clickable');
                $textSpan.click(function () {
                    if ($checkBox.is(':checked')) {
                        $checkBox.attr('checked', false);
                    } else {
                        $checkBox.attr('checked', true);
                    }

                    refreshCheckBoxValueAndText();
                });
            }

            return $containerDiv;
        },

        /* Creates a drop down list (combobox) input element for a field.
        *************************************************************************/
        _createDropDownListForField: function (field, fieldName, value) {
            var self = this;

            //Create a container div
            var $containerDiv = $('<div class="jtable-input jtable-dropdown-input"></div>');

            //Create select element
            var $select = $('<select class="' + field.inputClass + '" id="Edit-' + fieldName + '" name=' + fieldName + '></select>').appendTo($containerDiv);

            //add options
            var options = self._getOptionsWithCaching(fieldName);
            $.each(options, function (propName, propValue) {
                $select.append('<option value="' + propName + '"' + (propName == value ? ' selected="selected"' : '') + '>' + propValue + '</option>');
            });

            return $containerDiv;
        },
        /* Creates a radio button list for a field.
        *************************************************************************/
        _createRadioButtonListForField: function (field, fieldName, value) {
            var self = this;

            //Create a container div
            var $containerDiv = $('<div class="jtable-input jtable-radiobuttonlist-input"></div>');

            //add options
            var options = self._getOptionsWithCaching(fieldName);
            var radioButtonIndex = 0;
            $.each(options, function (propName, propValue) {
                var $radioButtonDiv = $('<div class="jtable-radio-input"></div>').appendTo($containerDiv);
                var $radioButton = $('<input type="radio" id="Edit-' + fieldName + (radioButtonIndex++) + '" class="' + field.inputClass + '" name="' + fieldName + '" value="' + propName + '"' + (propName == value ? ' checked="true"' : '') + ' />').appendTo($radioButtonDiv);
                var $textSpan = $('<span>' + propValue + '</span>').appendTo($radioButtonDiv);

                //Change checkbox state when clicked to text
                if (field.setOnTextClick != false) {
                    $textSpan.addClass('jtable-option-text-clickable');
                    $textSpan.click(function () {
                        if (!$radioButton.is(':checked')) {
                            $radioButton.attr('checked', true);
                        }
                    });
                }
            });

            return $containerDiv;
        },

        /* Gets display text for a checkbox field.
        *************************************************************************/
        _getCheckBoxTextForFieldByValue: function (fieldName, value) {
            var self = this;
            return self.options.fields[fieldName].values[value];
        },

        /* Returns true if given field's value must be checked state.
        *************************************************************************/
        _getIsCheckBoxSelectedForFieldByValue: function (fieldName, value) {
            var self = this;
            return (self._createCheckBoxStateArrayForFieldWithCaching(fieldName)[1].Value.toString() == value.toString());
        },

        /* Gets an object for a checkbox field that has Value and DisplayText
        *  properties.
        *************************************************************************/
        _getCheckBoxPropertiesForFieldByState: function (fieldName, checked) {
            var self = this;
            return self._createCheckBoxStateArrayForFieldWithCaching(fieldName)[(checked ? 1 : 0)];
        },

        /* Calls _createCheckBoxStateArrayForField with caching.
        *************************************************************************/
        _createCheckBoxStateArrayForFieldWithCaching: function (fieldName) {
            var self = this;

            var cacheKey = 'checkbox_' + fieldName;
            if (!self._cache[cacheKey]) {

                self._cache[cacheKey] = self._createCheckBoxStateArrayForField(fieldName);
            }

            return self._cache[cacheKey];
        },

        /* Creates a two element array of objects for states of a checkbox field.
        *  First element for unchecked state, second for checked state.
        *  Each object has two properties: Value and DisplayText
        *************************************************************************/
        _createCheckBoxStateArrayForField: function (fieldName) {
            var self = this;

            var stateArray = [];
            var currentIndex = 0;
            $.each(self.options.fields[fieldName].values, function (propName, propValue) {
                if (currentIndex++ <= 1) {
                    stateArray.push({ 'Value': propName, 'DisplayText': propValue });
                }
            });

            return stateArray;
        },

        /* Gets options from cache if exists, else downloads and caches.
        *************************************************************************/
        _getOptionsWithCaching: function (fieldName) {
            var self = this;

            var cacheKey = 'options_' + fieldName;
            if (!self._cache[cacheKey]) {
                var optionsSource = self.options.fields[fieldName].options;
                //Build options according to it's source type
                if (typeof optionsSource == 'string') {
                    //It is an Url to rownload options
                    self._cache[cacheKey] = self._downloadOptions(fieldName, optionsSource);
                } else if (jQuery.isArray(optionsSource)) {
                    //It is an array of options
                    self._cache[cacheKey] = self._buildOptionsFromArray(optionsSource);
                } else {
                    //It is an object that it's properties are options, so use directly this object
                    self._cache[cacheKey] = optionsSource;
                }
            }

            return self._cache[cacheKey];
        },

        /* Download options for a field from server according.
        *************************************************************************/
        _downloadOptions: function (fieldName, url) {
            var self = this;

            var options = {};
            $.ajax({
                url: url,
                type: 'POST',
                dataType: 'json',
                async: false,
                success: function (data) {
                    if (data.Result != 'OK') {
                        self._showError(data.Message);
                        return;
                    }

                    //Get options from incoming data
                    for (var i = 0; i < data.Options.length; i++) {
                        options[data.Options[i].Value] = data.Options[i].DisplayText;
                    }
                },
                error: function () {
                    var errMessage = self._formatString(self.options.messages.cannotLoadOptionsFor, fieldName);
                    self._showError(errMessage);
                }
            });

            return options;
        },

        /* Creates an options object (that it's property is value, value is displaytext)
        *  from a simple array.
        *************************************************************************/
        _buildOptionsFromArray: function (optionsArray) {
            var options = {};
            for (var i = 0; i < optionsArray.length; i++) {
                options[optionsArray[i]] = optionsArray[i];
            }

            return options;
        },

        /* Sets enabled/disabled state of a dialog button.
        *************************************************************************/
        _setEnabledOfDialogButton: function ($button, enabled, buttonText) {
            if (!$button) {
                return;
            }

            if (enabled != false) {
                $button.removeAttr('disabled').removeClass('ui-state-disabled');
            } else {
                $button.attr('disabled', 'disabled').addClass('ui-state-disabled');
            }

            if (buttonText) {
                $button.find('span').text(buttonText);
            }
        }

    });

})(jQuery);
/************************************************************************
* CREATE RECORD extension for jTable                                    *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {

            //Events
            recordAdded: function (event, data) { },

            //Localization
            messages: {
                addNewRecord: '+ Add new record'
            }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$addRecordDiv: null, //Reference to the adding new record dialog div

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Overrides base method to do create-specific constructions.
        *************************************************************************/
        _create: function () {
            var self = this;
            base._create.apply(this, arguments);

            self._createAddRecordDialogDiv();
        },

        /* Creates and prepares add new record dialog div
        *************************************************************************/
        _createAddRecordDialogDiv: function () {
            var self = this;

            //Check if createAction is supplied
            if (!self.options.actions.createAction) {
                return;
            }

            //Create a div for dialog and add to container element
            self._$addRecordDiv = $('<div></div>').appendTo(self._$mainContainer);

            //Prepare dialog
            self._$addRecordDiv.dialog({

                autoOpen: false,
                show: self.options.dialogShowEffect,
                hide: self.options.dialogHideEffect,
                width: 'auto',
                minWidth: '300',
                modal: true,
                title: self.options.messages.addNewRecord,
                buttons:
                        [{ //Cancel button
                            text: self.options.messages.cancel,
                            click: function () {
                                self._$addRecordDiv.dialog('close');
                            }
                        }, { //Save button
                            id: 'AddRecordDialogSaveButton',
                            text: self.options.messages.save,
                            click: function () {
                                var $saveButton = $('#AddRecordDialogSaveButton');
                                var $addRecordForm = self._$addRecordDiv.find('form');

                                if (self._trigger("formSubmitting", null, { form: $addRecordForm, formType: 'create' }) != false) {
                                    self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
                                    self._saveAddRecordForm($addRecordForm, $saveButton);
                                }
                            }
                        }],
                close: function () {
                    var $addRecordForm = self._$addRecordDiv.find('form').first();
                    var $saveButton = $('#AddRecordDialogSaveButton');
                    self._trigger("formClosed", null, { form: $addRecordForm, formType: 'create' });
                    self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                    $addRecordForm.remove();
                }
            });

            //If not 'add record button' supplied, create a new one.
            if (!self.options.addRecordButton) {
                self.options.addRecordButton = self._createAddRecordButton();
            }

            //Bind click event to show dialog form
            self.options.addRecordButton.click(function (e) {
                e.preventDefault();
                self._showAddRecordForm();
            });
        },

        /* Adds add new record button to the page.
        *************************************************************************/
        _createAddRecordButton: function () {
            var self = this;
            var $addRecordButton = $('<span class="jtable-add-record"><a href="#">' + self.options.messages.addNewRecord + '</a></span>').appendTo(self._$bottomPanel);
            return $addRecordButton;
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        /* Shows add new record dialog form.
        *************************************************************************/
        showCreateForm: function () {
            this._showAddRecordForm();
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Shows add new record dialog form.
        *************************************************************************/
        _showAddRecordForm: function () {
            var self = this;

            //Create add new record form
            var $addRecordForm = $('<form id="jtable-create-form" class="jtable-dialog-form jtable-create-form" action="' + self.options.actions.createAction + '" method="POST"></form>');

            //Create input elements
            for (var i = 0; i < self._fieldList.length; i++) {

                //Do not create input for fields that are not creatable
                if (self.options.fields[self._fieldList[i]].create == false) {
                    continue;
                }

                //Create a container div for this input field and add to form
                var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($addRecordForm);

                //Create a label for input
                $fieldContainer.append(self._createInputLabelForRecordField(self._fieldList[i]));

                //Create input element
                $fieldContainer.append(self._createInputForRecordField(self._fieldList[i]));
            }

            //Open the form
            self._$addRecordDiv.append($addRecordForm).dialog('open');
            self._trigger("formCreated", null, { form: $addRecordForm, formType: 'create' });
        },

        /* Saves new added record to the server and updates table.
        *************************************************************************/
        _saveAddRecordForm: function ($addRecordForm, $saveButton) {
            var self = this;

            //Make an Ajax call to update record
            $addRecordForm.data('submitting', true);
            $.ajax({
                url: $addRecordForm.attr('action'),
                data: $addRecordForm.serialize(),
                type: 'POST',
                dataType: 'json',
                success: function (data) {
                    //Check for errors
                    if (data.Result != 'OK') {
                        self._showError(data.Message);
                        self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                        return;
                    }

                    self._onRecordAdded(data);
                    self._addRowToTable(self._createRowFromRecord(data.Record), null, true);
                    self._$addRecordDiv.dialog("close");
                },
                error: function () {
                    self._showError(self.options.messages.serverCommunicationError);
                    self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                }
            });
        },

        _onRecordAdded: function (data) {
            this._trigger("recordAdded", null, { record: data.Record, serverResponse: data });
        }

    });

})(jQuery);
/************************************************************************
* EDIT RECORD extension for jTable                                      *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create,
        _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
        _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {

            //Options

            //Events
            recordUpdated: function (event, data) { },
            rowUpdated: function (event, data) { },

            //Localization
            messages: {
                editRecord: 'Edit Record'
            }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$editDiv: null, //Reference to the editing dialog div
        _$editingRow: null, //Reference to currently editing row

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Overrides base method to do editing-specific constructions.
        *************************************************************************/
        _create: function () {
            var self = this;
            base._create.apply(this, arguments);

            self._createEditDialogDiv();
        },

        /* Creates and prepares edit dialog div
        *************************************************************************/
        _createEditDialogDiv: function () {
            var self = this;

            //Create a div for dialog and add to container element
            self._$editDiv = $('<div></div>').appendTo(self._$mainContainer);

            //Prepare dialog
            self._$editDiv.dialog({
                autoOpen: false,
                show: self.options.dialogShowEffect,
                hide: self.options.dialogHideEffect,
                width: 'auto',
                minWidth: '300',
                modal: true,
                title: self.options.messages.editRecord,
                buttons:
                        [{  //cancel button
                            text: self.options.messages.cancel,
                            click: function () {
                                self._$editDiv.dialog('close');
                            }
                        }, { //save button
                            id: 'EditDialogSaveButton',
                            text: self.options.messages.save,
                            click: function () {

                                var $saveButton = $('#EditDialogSaveButton');
                                var $editForm = self._$editDiv.find('form');

                                if (self._trigger("formSubmitting", null, { form: $editForm, formType: 'edit' }) != false) {
                                    self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
                                    self._saveEditForm($editForm, $saveButton);
                                }
                            }

                        }],
                close: function () {
                    var $editForm = self._$editDiv.find('form').first();
                    var $saveButton = $('#EditDialogSaveButton');
                    self._trigger("formClosed", null, { form: $editForm, formType: 'edit' });
                    self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                    $editForm.remove();
                }
            });
        },

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        /* Overrides base method to add a 'editing column cell' to header row.
        *************************************************************************/
        _addColumnsToHeaderRow: function ($tr) {
            var self = this;
            base._addColumnsToHeaderRow.apply(this, arguments);

            //Add empty command cell for edit button column
            if (self.options.actions.updateAction != undefined) {
                $tr.append(self._createEmptyHeader());
            }
        },

        /* Overrides base method to add a 'edit command cell' to a row.
        *************************************************************************/
        _addCellsToRowUsingRecord: function ($row) {
            var self = this;
            base._addCellsToRowUsingRecord.apply(this, arguments);

            //Add empty command column headers for edit and delete buttons
            if (self.options.actions.updateAction != undefined) {
                var $editCell = $('<td class="jtable-command-column"></td>').appendTo($row);
                $('<button class="jtable-command-button jtable-edit-command-button" title="' + self.options.messages.editRecord + '"><span>' + self.options.messages.editRecord + '</span></button>')
                    .appendTo($editCell)
                    .click(function (e) {
                        e.preventDefault();
                        e.stopPropagation();
                        self._showEditForm($row);
                    });
            }
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Shows edit form for a row.
        *************************************************************************/
        _showEditForm: function ($tableRow) {
            var self = this;
            var record = $tableRow.data('record');

            //Create edit form
            var $editForm = $('<form id="jtable-edit-form" class="jtable-dialog-form jtable-edit-form" action="' + self.options.actions.updateAction + '" method="POST"></form>');

            //Create input fields
            for (var i = 0; i < self._fieldList.length; i++) {

                //Create hidden input for 'key' field
                if (self.options.fields[self._fieldList[i]].key == true) {
                    $editForm.append(self._createInputForHidden(self._fieldList[i], record[self._fieldList[i]]));
                    continue;
                }

                //Do not create element for non-editable fields
                if (self.options.fields[self._fieldList[i]].edit == false) {
                    continue;
                }

                //Create a container div for this input field and add to form
                var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($editForm);

                //Create a label for input
                $fieldContainer.append(self._createInputLabelForRecordField(self._fieldList[i]));

                //Create input element with it's current value
                var currentValue = self._getValueForRecordField(record, self._fieldList[i]);
                $fieldContainer.append(self._createInputForRecordField(self._fieldList[i], currentValue, record));
            }

            //Open dialog
            self._$editingRow = $tableRow;
            self._$editDiv.append($editForm).dialog('open');
            self._trigger("formCreated", null, { form: $editForm, formType: 'edit', record: record });
        },

        /* Saves editing form to the server and updates the record on the table.
        *************************************************************************/
        _saveEditForm: function ($editForm, $saveButton) {
            var self = this;

            //Make an Ajax call to update record
            $.ajax({
                url: $editForm.attr('action'),
                data: $editForm.serialize(),
                type: 'POST',
                dataType: 'json',
                success: function (data) {
                    //Check for errors
                    if (data.Result != 'OK') {
                        self._showError(data.Message);
                        self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                        return;
                    }

                    var $editingRow = self._$editingRow;

                    self._updateRecordValuesFromEditForm($editingRow.data('record'), $editForm);
                    self._updateRowTexts($editingRow);

                    self._trigger("recordUpdated", null, { record: $editingRow.data('record'), row: $editingRow, serverResponse: data });

                    self._showUpdateAnimationForRow($editingRow);

                    self._$editDiv.dialog("close");
                },
                error: function () {
                    self._showError(self.options.messages.serverCommunicationError);
                    self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
                }
            });
        },

        /* Updates values of a record from given edit form
        *************************************************************************/
        _updateRecordValuesFromEditForm: function (record, $form) {
            var self = this;

            //Update record from given form
            for (var i = 0; i < self._fieldList.length; i++) {

                var fieldName = self._fieldList[i];
                var field = self.options.fields[fieldName];

                //Do not update non-editable fields
                if (field.edit == false) {
                    continue;
                }

                //Get field name and the input element of this field in the form
                var $inputElement = $form.find('[name="' + fieldName + '"]');

                //Update field in record according to it's type
                if (field.type == 'date') {
                    var displayFormat = field.displayFormat || self.options.defaultDateFormat;
                    try {
                        var date = $.datepicker.parseDate(displayFormat, $inputElement.val());
                        record[fieldName] = '/Date(' + date.getTime() + ')/';
                    } catch (e) {
                        //TODO: Handle incorrect/different date formats
                        record[fieldName] = '/Date(' + (new Date()).getTime() + ')/';
                    }
                } else if (field.options && field.type == 'radiobutton') {
                    var $checkedElement = $inputElement.filter('[checked="true"]');
                    if ($checkedElement.length) {
                        record[fieldName] = $checkedElement.val();
                    } else {
                        record[fieldName] = undefined;
                    }
                } else {
                    record[fieldName] = $inputElement.val();
                }
            }
        },

        /* Gets text for a field of a record according to it's type.
        *************************************************************************/
        _getValueForRecordField: function (record, fieldName) {
            var self = this;

            var field = self.options.fields[fieldName];
            var fieldValue = record[fieldName];

            if (field.type == 'date') {
                return self._getDisplayTextForDateRecordField(field, fieldValue);
            } else {
                return fieldValue;
            }
        },

        /* Updates cells of a table row's text values from row's record values.
        *************************************************************************/
        _updateRowTexts: function ($tableRow) {
            var self = this;

            var record = $tableRow.data('record');
            var $columns = $tableRow.find('td');
            for (var i = 0; i < self._columnList.length; i++) {
                var displayItem = self._getDisplayTextForRecordField(record, self._columnList[i]);
                $columns.eq(self._firstDataColumnOffset + i).empty().append(displayItem || '');
            }

            self._onRowUpdated($tableRow);
        },

        /* Shows 'updated' animation for a table row.
        *************************************************************************/
        _showUpdateAnimationForRow: function ($tableRow) {
            $tableRow.stop(true, true).addClass('jtable-row-updated', 'slow', '', function () {
                $tableRow.removeClass('jtable-row-updated', 5000);
            });
        },


        /************************************************************************
        * EVENT RAISING METHODS                                                 *
        *************************************************************************/

        _onRowUpdated: function ($row) {
            this._trigger("rowUpdated", null, { row: $row, record: $row.data('record') });
        }

    });

})(jQuery);
/************************************************************************
* DELETION extension for jTable                                         *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create,
        _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
        _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {

            deleteConfirmation: true,

            recordDeleted: function (event, data) { },

            messages: {
                deleteConfirmation: 'This record will be deleted. Are you sure?',
                deleteText: 'Delete',
                deleting: 'Deleting',
                canNotDeletedRecords: 'Can not deleted {0} of {1} records!',
                deleteProggress: 'Deleted {0} of {1} records, processing...'
            }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$deleteRecordDiv: null, //Reference to the adding new record dialog div
        _$deletingRow: null, //Reference to currently deleting row

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Overrides base method to do deletion-specific constructions.
        *************************************************************************/
        _create: function () {
            var self = this;
            base._create.apply(this, arguments);

            self._createDeleteDialogDiv();
        },

        /* Creates and prepares delete record confirmation dialog div.
        *************************************************************************/
        _createDeleteDialogDiv: function () {
            var self = this;

            //Create div element for delete confirmation dialog
            self._$deleteRecordDiv =
                $('<div><p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span><span class="jtable-delete-confirm-message"></p></div>').appendTo(self._$mainContainer);

            //Prepare dialog
            self._$deleteRecordDiv.dialog({
                autoOpen: false,
                show: self.options.dialogShowEffect,
                hide: self.options.dialogHideEffect,
                modal: true,
                title: self.options.messages.areYouSure,
                buttons:
                        [{  //cancel button
                            text: self.options.messages.cancel,
                            click: function () {
                                self._$deleteRecordDiv.dialog("close");
                            }
                        }, {//delete button
                            id: 'DeleteDialogButton',
                            text: self.options.messages.deleteText,
                            click: function () {
                                var $deleteButton = $('#DeleteDialogButton');
                                self._setEnabledOfDialogButton($deleteButton, false, self.options.messages.deleting);

                                self._deleteRecordFromServer(
                                    self._$deletingRow,
                                    function () {
                                        self._removeRowsFromTableWithAnimation(self._$deletingRow);
                                        self._$deleteRecordDiv.dialog('close');
                                    },
                                    function (message) { //error
                                        self._showError(message);
                                        self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
                                    }
                                );
                            }
                        }],
                close: function () {
                    var $deleteButton = $('#DeleteDialogButton');
                    self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
                }
            });
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        deleteRows: function ($rows) {
            var self = this;

            if ($rows.length <= 0 || self._isBusy()) {
                return;
            }

            if ($rows.length == 1) {
                self._deleteRecordFromServer(
                    $rows,
                    function () { //success
                        self._removeRowsFromTableWithAnimation($rows);
                    },
                    function (message) { //error
                        self._showError(message);
                    }
                );
                return;
            }

            self._showBusy(self._formatString(self.options.messages.deleteProggress, 0, $rows.length));

            var completedCount = 0;
            var deletedCount = 0;
            var isCompleted = function () {
                return (completedCount >= $rows.length);
            };

            var completed = function () {
                var $deletedRows = $rows.filter('.jtable-row-ready-to-remove');
                if ($deletedRows.length < $rows.length) {
                    self._showError(self._formatString(self.options.messages.canNotDeletedRecords, $rows.length - $deletedRows.length, $rows.length));
                }

                if ($deletedRows.length > 0) {
                    self._removeRowsFromTableWithAnimation($deletedRows);
                }

                self._hideBusy();
            };

            $rows.each(function () {
                var $row = $(this);
                self._deleteRecordFromServer(
                    $row,
                    function () { //success
                        ++deletedCount;
                        ++completedCount;
                        $row.addClass('jtable-row-ready-to-remove');
                        self._showBusy(self._formatString(self.options.messages.deleteProggress, deletedCount, $rows.length));
                        if (isCompleted()) {
                            completed();
                        }
                    },
                    function () { //error
                        ++completedCount;
                        if (isCompleted()) {
                            completed();
                        }
                    }
                );
            });
        },

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        /* Overrides base method to add a 'deletion column cell' to header row.
        *************************************************************************/
        _addColumnsToHeaderRow: function ($tr) {
            var self = this;
            base._addColumnsToHeaderRow.apply(this, arguments);

            if (self.options.actions.deleteAction != undefined) {
                $tr.append(self._createEmptyHeader());
            }
        },

        /* Overrides base method to add a 'delete command cell' to a row.
        *************************************************************************/
        _addCellsToRowUsingRecord: function ($row) {
            var self = this;
            base._addCellsToRowUsingRecord.apply(this, arguments);

            if (self.options.actions.deleteAction != undefined) {
                var $deleteCell = $('<td class="jtable-command-column"></td>').appendTo($row);
                $('<button class="jtable-command-button jtable-delete-command-button" title="' + self.options.messages.deleteText + '"><span>' + self.options.messages.deleteText + '</span></button>')
                    .appendTo($deleteCell)
                    .click(function (e) {
                        e.preventDefault();
                        e.stopPropagation();
                        self._deleteButtonClickedForRow($row);
                    });
            }
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* This method is called when user clicks delete button on a row.
        *************************************************************************/
        _deleteButtonClickedForRow: function ($row) {
            var self = this;

            var deleteConfirm;
            var deleteConfirmMessage = self.options.messages.deleteConfirmation;

            if ($.isFunction(self.options.deleteConfirmation)) {
                var data = { row: $row, record: $row.data('record'), deleteConfirm: true, deleteConfirmMessage: deleteConfirmMessage, cancel: false, cancelMessage: null };
                self.options.deleteConfirmation(data);

                if (data.cancel) {
                    if (data.cancelMessage) {
                        self._showError(data.cancelMessage); //TODO: show warning/stop message instead of error (also show warning/error ui icon)!
                    }
                    return;
                }

                deleteConfirmMessage = data.deleteConfirmMessage;
                deleteConfirm = data.deleteConfirm;
            } else {
                deleteConfirm = self.options.deleteConfirmation;
            }

            if (deleteConfirm != false) {
                self._$deleteRecordDiv.find('.jtable-delete-confirm-message').html(deleteConfirmMessage);
                self._showDeleteDialog($row);
            } else {
                self._deleteRecordFromServer(
                    $row,
                    function () { //success
                        self._removeRowsFromTableWithAnimation($row);
                    },
                    function (message) { //error
                        self._showError(message);
                    }
                );
            }
        },

        /* Shows delete comfirmation dialog.
        *************************************************************************/
        _showDeleteDialog: function ($row) {
            var self = this;

            self._$deletingRow = $row;
            self._$deleteRecordDiv.dialog('open');
        },

        /* Performs an ajax call to server to delete record
        *  and removesd row of record from table if ajax call success.
        *************************************************************************/
        _deleteRecordFromServer: function ($row, success, error) {
            var self = this;

            //Check if it is already being deleted right now
            if ($row.data('deleting') == true) {
                return;
            }

            $row.data('deleting', true);

            //Get value of key field for this row
            var keyValue = $row.data('record')[self._keyField];

            //Make ajax request
            $.ajax({
                url: self.options.actions.deleteAction,
                data: self._keyField + '=' + keyValue,
                type: 'POST',
                dataType: 'json',
                success: function (data) {
                    //Check for errors
                    if (data.Result != 'OK') {
                        $row.data('deleting', false);
                        if (error) {
                            error(data.Message);
                        }

                        return;
                    }

                    self._trigger("recordDeleted", null, { record: $row.data('record'), row: $row, serverResponse: data });

                    if (success) {
                        success();
                    }
                },
                error: function () {
                    $row.data('deleting', false);
                    if (error) {
                        error(self.options.messages.serverCommunicationError);
                    }
                }
            });
        },

        /* Removes a row from table after a 'deleting' animation.
        *************************************************************************/
        _removeRowsFromTableWithAnimation: function ($rows) {
            var self = this;

            //Stop current animation (if does exists) and begin 'deleting' animation.
            $rows.stop(true, true).addClass('jtable-row-deleting', 'slow', '').promise().done(function () {
                self._removeRowsFromTable($rows, 'deleted');
            });
        }
    });

})(jQuery);
/************************************************************************
* SELECTING extension for jTable                                        *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create,
        _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
        _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord,
        _onLoadingRecords: $.hik.jtable.prototype._onLoadingRecords,
        _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded,
        _onRowsRemoved: $.hik.jtable.prototype._onRowsRemoved
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {

            //Options
            selecting: false,
            multiselect: false,
            selectingCheckboxes: false,
            selectOnRowClick: true,

            //Events
            selectionChanged: function (event, data) { }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _selectedRecordIdsBeforeLoad: [], //This list is used to store selected row Id's to restore them after a page refresh.
        _$selectAllCheckbox: null, //Reference to the 'select/deselect all' checkbox
        _shiftKeyDown: false,

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Overrides base method to do deletion-specific constructions.
        *************************************************************************/
        _create: function () {
            if (this.options.selecting && this.options.selectingCheckboxes) {
                ++this._firstDataColumnOffset;
            }

            this._bindKeyboardEvents();

            base._create.apply(this, arguments);
        },

        _bindKeyboardEvents: function () {
            var self = this;
            $(document).keydown(function (event) {
                switch (event.which) {
                    case 16: //shift key
                        self._shiftKeyDown = true;
                        break;
                }
            }).keyup(function (event) {
                switch (event.which) {
                    case 16: //shift key
                        self._shiftKeyDown = false;
                        break;
                }
            });
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        /* Gets jQuery selection for currently selected rows.
        *************************************************************************/
        selectedRows: function () {
            var self = this;
            return self._getSelectedRows();
        },

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        /* Overrides base method to add a 'select column' to header row.
        *************************************************************************/
        _addColumnsToHeaderRow: function ($tr) {
            var self = this;

            //Add 'select' column header
            if (self.options.selecting && self.options.selectingCheckboxes) {
                if (self.options.multiselect) {
                    $tr.append(self._createSelectAllHeader());
                } else {
                    $tr.append(self._createEmptyHeader());
                }
            }

            base._addColumnsToHeaderRow.apply(this, arguments);
        },

        /* Overrides base method to add a 'delete command cell' to a row.
        *************************************************************************/
        _addCellsToRowUsingRecord: function ($row) {
            var self = this;

            if (self.options.selecting) {
                self._makeRowSelectable($row);
            }

            base._addCellsToRowUsingRecord.apply(this, arguments);
        },

        _onLoadingRecords: function () {
            var self = this;
            self._storeSelectionList();
            base._onLoadingRecords.apply(this, arguments);
        },

        _onRecordsLoaded: function () {
            var self = this;
            self._restoreSelectionList();
            base._onRecordsLoaded.apply(this, arguments);
        },

        _onRowsRemoved: function ($rows, reason) {
            var self = this;

            if ((reason != 'reloading') && self.options.selecting && ($rows.filter('.jtable-row-selected').length > 0)) {
                self._onSelectionChanged();
            }

            base._onRowsRemoved.apply(this, arguments);
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Creates a header column to select/deselect all rows.
        *************************************************************************/
        _createSelectAllHeader: function () {
            var self = this;

            var $columnHeader = $('<th class="jtable-column-header jtable-column-header-selecting"></th>');
            var $headerContainer = $('<div class="jtable-column-header-container"></div>').appendTo($columnHeader);
            self._$selectAllCheckbox = $('<input type="checkbox" />').appendTo($headerContainer);

            self._$selectAllCheckbox.click(function () {
                if (self._$tableRows.length <= 0) {
                    self._$selectAllCheckbox.attr('checked', false);
                    return;
                }

                var allRows = self._$tableBody.find('tr');
                if (self._$selectAllCheckbox.is(':checked')) {
                    self._selectRows(allRows);
                } else {
                    self._deselectRows(allRows);
                }

                self._onSelectionChanged();
            });

            return $columnHeader;
        },

        /* Stores Id's of currently selected records to _selectedRecordIdsBeforeLoad.
        *************************************************************************/
        _storeSelectionList: function () {
            var self = this;

            if (!self.options.selecting) {
                return;
            }

            self._selectedRecordIdsBeforeLoad = [];
            self._getSelectedRows().each(function () {
                self._selectedRecordIdsBeforeLoad.push($(this).data('record')[self._keyField]);
            });
        },

        /* Selects rows whose Id is in _selectedRecordIdsBeforeLoad;
        *************************************************************************/
        _restoreSelectionList: function () {
            var self = this;

            if (!self.options.selecting) {
                return;
            }

            var selectedRowCount = 0;
            for (var i = 0; i < self._$tableRows.length; ++i) {
                if ($.inArray(self._$tableRows[i].data('record')[self._keyField], self._selectedRecordIdsBeforeLoad) > -1) {
                    self._selectRows(self._$tableRows[i]);
                    ++selectedRowCount;
                }
            }

            if (self._selectedRecordIdsBeforeLoad.length > 0 && self._selectedRecordIdsBeforeLoad.length != selectedRowCount) {
                self._onSelectionChanged();
            }

            self._selectedRecordIdsBeforeLoad = [];
            self._refreshSelectAllCheckboxState();
        },

        /* Gets all selected rows.
        *************************************************************************/
        _getSelectedRows: function () {
            var self = this;
            return self._$tableBody.find('.jtable-row-selected');
        },

        /* Adds selectable feature to a row.
        *************************************************************************/
        _makeRowSelectable: function ($row) {
            var self = this;

            //Select/deselect on row click
            if (self.options.selectOnRowClick) {
                $row.click(function () {
                    self._invertRowSelection($row);
                });
            }

            //'select/deselect' checkbox column
            if (self.options.selectingCheckboxes) {
                var $cell = $('<td class="jtable-selecting-column"></td>');
                var $selectCheckbox = $('<input type="checkbox" />').appendTo($cell);

                if (!self.options.selectOnRowClick) {
                    $selectCheckbox.click(function () {
                        self._invertRowSelection($row);
                    });
                }

                $row.append($cell);
            }
        },

        /* Inverts selection state of a single row.
        *************************************************************************/
        _invertRowSelection: function ($row) {
            var self = this;

            if ($row.hasClass('jtable-row-selected')) {
                self._deselectRows($row);
            } else {
                if (self._shiftKeyDown) {
                    var rowIndex = self._findRowIndex($row);
                    //try to select row and above rows until first selected row
                    var beforeIndex = self._findFirstSelectedRowIndexBeforeIndex(rowIndex) + 1;
                    if (beforeIndex > 0 && beforeIndex < rowIndex) {
                        self._selectRows(self._$tableBody.find('tr').slice(beforeIndex, rowIndex + 1));
                    } else {
                        //try to select row and below rows until first selected row
                        var afterIndex = self._findFirstSelectedRowIndexAfterIndex(rowIndex) - 1;
                        if (afterIndex > rowIndex) {
                            console.log(rowIndex);
                            console.log(afterIndex);
                            self._selectRows(self._$tableBody.find('tr').slice(rowIndex, afterIndex + 1));
                        } else {
                            //just select this row
                            self._selectRows($row);
                        }
                    }
                } else {
                    self._selectRows($row);
                }
            }

            self._onSelectionChanged();
        },

        _findFirstSelectedRowIndexBeforeIndex: function (rowIndex) {
            var self = this;

            for (var i = rowIndex - 1; i >= 0; --i) {
                if (self._$tableRows[i].hasClass('jtable-row-selected')) {
                    return i;
                }
            }

            return -1;
        },

        _findFirstSelectedRowIndexAfterIndex: function (rowIndex) {
            var self = this;

            for (var i = rowIndex + 1; i < self._$tableRows.length; ++i) {
                if (self._$tableRows[i].hasClass('jtable-row-selected')) {
                    return i;
                }
            }

            return -1;
        },

        /* Makes row/rows 'selected'.
        *************************************************************************/
        _selectRows: function ($rows) {
            var self = this;

            if (!self.options.multiselect) {
                self._deselectRows(self._getSelectedRows());
            }

            $rows.addClass('jtable-row-selected');

            if (self.options.selectingCheckboxes) {
                $rows.find('td.jtable-selecting-column input').attr('checked', true);
            }

            self._refreshSelectAllCheckboxState();
        },

        /* Makes row/rows 'non selected'.
        *************************************************************************/
        _deselectRows: function ($rows) {
            var self = this;

            $rows.removeClass('jtable-row-selected');

            if (self.options.selectingCheckboxes) {
                $rows.find('td.jtable-selecting-column input').removeAttr('checked');
            }

            self._refreshSelectAllCheckboxState();
        },

        /* Updates state of the 'select/deselect' all checkbox according to count of selected rows.
        *************************************************************************/
        _refreshSelectAllCheckboxState: function () {
            var self = this;

            if (!self.options.selectingCheckboxes || !self.options.multiselect) {
                return;
            }

            var totalRowCount = self._$tableRows.length;
            var selectedRowCount = self._getSelectedRows().length;

            if (selectedRowCount == 0) {
                self._$selectAllCheckbox.prop('indeterminate', false);
                self._$selectAllCheckbox.attr('checked', false);
            } else if (selectedRowCount == totalRowCount) {
                self._$selectAllCheckbox.prop('indeterminate', false);
                self._$selectAllCheckbox.attr('checked', true);
            } else {
                self._$selectAllCheckbox.attr('checked', false);
                self._$selectAllCheckbox.prop('indeterminate', true);
            }
        },

        /* Raises 'selectionChanged' event.
        *************************************************************************/
        _onSelectionChanged: function () {
            var self = this;
            self._trigger("selectionChanged", null, {});
        }

    });

})(jQuery);
/************************************************************************
* PAGING extension for jTable                                           *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        load: $.hik.jtable.prototype.load,
        _create: $.hik.jtable.prototype._create,
        _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl,
        _addRowToTable: $.hik.jtable.prototype._addRowToTable,
        _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable,
        _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {
            paging: false,
            pageSize: 10,

            messages: {
                pagingInfo: 'Showing {0} to {1} of {2} records'
            }
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _$pagingListArea: null, //Reference to the page list area in to bottom panel
        _totalRecordCount: 0, //Total count of records on all pages
        _currentPageNo: 1, //Current page number

        /************************************************************************
        * CONSTRUCTOR                                                           *
        *************************************************************************/

        /* Overrides base method to do deletion-specific constructions.
        *************************************************************************/
        _create: function () {
            var self = this;
            base._create.apply(this, arguments);

            self._createPageListArea();
        },

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        load: function () {
            this._currentPageNo = 1;
            base.load.apply(this, arguments);
        },

        _createRecordLoadUrl: function () {
            var self = this;
            var loadUrl = base._createRecordLoadUrl.apply(this, arguments);

            loadUrl = self._addPagingInfoToUrl(loadUrl, self._currentPageNo);

            return loadUrl;
        },

        _addRowToTable: function ($tableRow, index, isNewRow) {
            
            if (isNewRow && this.options.paging) {
                this._reloadTable();
                return;
            }

            base._addRowToTable.apply(this, arguments);
        },

        _removeRowsFromTable: function ($rows, reason) {
            base._removeRowsFromTable.apply(this, arguments);

            if (this.options.paging) {
                if (this._$tableRows.length <= 0 && this._currentPageNo > 1) {
                    --this._currentPageNo;
                }

                this._reloadTable();
            }
        },

        _onRecordsLoaded: function (data) {
            this._totalRecordCount = data.TotalRecordCount;
            this._createPagingList();

            base._onRecordsLoaded.apply(this, arguments);
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Creates page list area if paging enabled.
        *************************************************************************/
        _createPageListArea: function () {
            var self = this;

            if (!self.options.paging) {
                return;
            }

            self._$pagingListArea = $('<span class="jtable-page-list"></span>').appendTo(self._$bottomPanel);
        },

        /* Adds jtStartIndex and jtPageSize parameters to a URL as query string.
        *************************************************************************/
        _addPagingInfoToUrl: function (url, pageNumber) {
            var self = this;

            if (!self.options.paging) {
                return url;
            }

            var jtStartIndex = (pageNumber - 1) * self.options.pageSize;
            var jtPageSize = self.options.pageSize;

            return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtStartIndex=' + jtStartIndex + '&jtPageSize=' + jtPageSize);
        },

        /* Creates and shows the page list.
        *************************************************************************/
        _createPagingList: function () {
            var self = this;

            if (!self.options.paging || self.options.pageSize <= 0) {
                return;
            }

            self._$pagingListArea.empty();

            var pageCount = self._calculatePageCount();

            self._createFirstAndPreviousPageButtons();
            self._createPageNumberButtons(self._calculatePageNumbers(pageCount));
            self._createLastAndNextPageButtons(pageCount);
            self._createPagingInfo();
            self._bindClickEventsToPageNumberButtons();
        },

        /* Creates and shows previous and first page links.
        *************************************************************************/
        _createFirstAndPreviousPageButtons: function () {
            var self = this;

            if (self._currentPageNo > 1) {
                $('<span class="jtable-page-number-first">|&lt</span>')
                    .data('pageNumber', 1)
                    .appendTo(self._$pagingListArea);
                $('<span class="jtable-page-number-previous">&lt</span>')
                    .data('pageNumber', self._currentPageNo - 1)
                    .appendTo(self._$pagingListArea);
            }
        },

        /* Creates and shows next and last page links.
        *************************************************************************/
        _createLastAndNextPageButtons: function (pageCount) {
            var self = this;

            if (self._currentPageNo < pageCount) {
                $('<span class="jtable-page-number-next">&gt</span>')
                    .data('pageNumber', self._currentPageNo + 1)
                    .appendTo(self._$pagingListArea);
                $('<span class="jtable-page-number-last">&gt|</span>')
                    .data('pageNumber', pageCount)
                    .appendTo(self._$pagingListArea);
            }
        },

        /* Creates and shows page number links for given number array.
        *************************************************************************/
        _createPageNumberButtons: function (pageNumbers) {
            var self = this;

            var previousNumber = 0;
            for (var i = 0; i < pageNumbers.length; i++) {

                //Create "..." between page numbers if needed
                if ((pageNumbers[i] - previousNumber) > 1) {
                    $('<span class="jtable-page-number-space">...</span>').appendTo(self._$pagingListArea);
                }

                previousNumber = pageNumbers[i];

                self._createPageNumberButton(pageNumbers[i]);
            }
        },

        /* Creates a page number link and adds to paging area.
        *************************************************************************/
        _createPageNumberButton: function (pageNumber) {
            var self = this;

            $('<span class="' + (self._currentPageNo == pageNumber ? 'jtable-page-number-active' : 'jtable-page-number') + '">' + pageNumber + '</span>')
                .data('pageNumber', pageNumber)
                .appendTo(self._$pagingListArea);
        },

        /* Calculates total page count according to page size and total record count.
        *************************************************************************/
        _calculatePageCount: function () {
            var self = this;

            var pageCount = Math.floor(self._totalRecordCount / self.options.pageSize);
            if (self._totalRecordCount % self.options.pageSize != 0) {
                ++pageCount;
            }

            return pageCount;
        },

        /* Calculates page numbers and returns an array of these numbers.
        *************************************************************************/
        _calculatePageNumbers: function (pageCount) {
            var self = this;

            if (pageCount <= 6) {
                //Show all pages
                var pageNumbers = [];
                for (var i = 1; i <= pageCount; ++i) {
                    pageNumbers.push(i);
                }

                return pageNumbers;
            } else {
                //show first three, last three, current, previous and next page numbers
                var shownPageNumbers = [1, 2, 3, pageCount - 2, pageCount - 1, pageCount];
                var previousPageNo = self._normalizeNumber(self._currentPageNo - 1, 1, pageCount, 1);
                var nextPageNo = self._normalizeNumber(self._currentPageNo + 1, 1, pageCount, 1);

                self._insertToArrayIfDoesNotExists(shownPageNumbers, previousPageNo);
                self._insertToArrayIfDoesNotExists(shownPageNumbers, self._currentPageNo);
                self._insertToArrayIfDoesNotExists(shownPageNumbers, nextPageNo);

                shownPageNumbers.sort(function (a, b) { return a - b; });
                return shownPageNumbers;
            }
        },

        /* Creates and shows paging informations.
        *************************************************************************/
        _createPagingInfo: function () {
            var self = this;

            var startNo = (self._currentPageNo - 1) * self.options.pageSize + 1;
            var endNo = self._currentPageNo * self.options.pageSize;
            endNo = self._normalizeNumber(endNo, startNo, self._totalRecordCount, 0);

            if (endNo >= startNo) {
                var pagingInfoMessage = self._formatString(self.options.messages.pagingInfo, startNo, endNo, self._totalRecordCount);
                $('<span class="jtable-page-info">' + pagingInfoMessage + '</span>').appendTo(self._$pagingListArea);
            }
        },

        /* Binds click events of all page links to change the page.
        *************************************************************************/
        _bindClickEventsToPageNumberButtons: function () {
            var self = this;

            self._$pagingListArea
                .find('.jtable-page-number,.jtable-page-number-previous,.jtable-page-number-next,.jtable-page-number-first,.jtable-page-number-last')
                .click(function (e) {
                    e.preventDefault();
                    var $this = $(this);
                    self._currentPageNo = $this.data('pageNumber');
                    self._reloadTable();
                });
        }

    });

})(jQuery);
/************************************************************************
* SORTING extension for jTable                                          *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _create: $.hik.jtable.prototype._create,
        _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
        _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
        _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {
            sorting: false,
            defaultSorting: ''
        },

        /************************************************************************
        * PRIVATE FIELDS                                                        *
        *************************************************************************/

        _lastSorting: '', //Last sorting of the table

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        _normalizeFieldOptions: function (fieldName, props) {
            base._normalizeFieldOptions.apply(this, arguments);

            props.sorting = (props.sorting != false);
        },

        _createHeaderCellForField: function (fieldName, field) {
            var self = this;
            var $headerCell = base._createHeaderCellForField.apply(this, arguments);

            if (self.options.sorting && field.sorting) {
                self._makeColumnSortable($headerCell, fieldName);
            }

            return $headerCell;
        },

        _createRecordLoadUrl: function () {
            var self = this;
            var loadUrl = base._createRecordLoadUrl.apply(this, arguments);

            loadUrl = self._addSortingInfoToUrl(loadUrl);

            return loadUrl;
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        /* Makes a column sortable.
        *************************************************************************/
        _makeColumnSortable: function ($columnHeader, fieldName) {
            var self = this;

            $columnHeader.addClass('jtable-column-header-sortable');

            //Default sorting?
            if (self.options.defaultSorting.indexOf(fieldName) > -1) {
                if (self.options.defaultSorting.indexOf('DESC') > -1) {
                    $columnHeader.addClass('jtable-column-header-sorted-desc');
                    self._lastSorting = fieldName + " DESC";
                } else {
                    $columnHeader.addClass('jtable-column-header-sorted-asc');
                    self._lastSorting = fieldName + " ASC";
                }
            }

            $columnHeader.click(function (e) {
                e.preventDefault();
                self._sortTableByColumn($columnHeader);
            });
        },

        /* Sorts table according to a column header.
        *************************************************************************/
        _sortTableByColumn: function ($columnHeader) {
            var self = this;

            //Remove sorting styles from all columns except this one
            $columnHeader.siblings().removeClass('jtable-column-header-sorted-asc jtable-column-header-sorted-desc');

            //Sort ASC or DESC according to current sorting state
            if ($columnHeader.hasClass('jtable-column-header-sorted-asc')) {
                $columnHeader.removeClass('jtable-column-header-sorted-asc').addClass('jtable-column-header-sorted-desc');
                self._lastSorting = $columnHeader.data('fieldName') + " DESC";
            } else {
                $columnHeader.removeClass('jtable-column-header-sorted-desc').addClass('jtable-column-header-sorted-asc');
                self._lastSorting = $columnHeader.data('fieldName') + " ASC";
            }

            //Load current page again
            self._reloadTable();
        },

        /* Adds jtSorting parameter to a URL as query string.
        *************************************************************************/
        _addSortingInfoToUrl: function (url) {
            var self = this;

            if (!self.options.sorting || self._lastSorting == '') {
                return url;
            }

            return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtSorting=' + self._lastSorting);
        }

    });

})(jQuery);
/************************************************************************
* MASTER/CHILD tables extension for jTable                              *
*************************************************************************/
(function ($) {

    //Reference to base object members
    var base = {
        _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable
    };

    //extension members
    $.extend(true, $.hik.jtable.prototype, {

        /************************************************************************
        * DEFAULT OPTIONS / EVENTS                                              *
        *************************************************************************/
        options: {
            openChildAsAccordion: false
        },

        /************************************************************************
        * PUBLIC METHODS                                                        *
        *************************************************************************/

        openChildTable: function ($row, tableOptions, opened) {
            var self = this;

            if (tableOptions.showCloseButton == undefined) {
                tableOptions.showCloseButton = true;
            }

            if (tableOptions.showCloseButton && !tableOptions.closeRequested) {
                tableOptions.closeRequested = function () {
                    self.closeChildTable($row);
                };
            }
            
            if (self.options.openChildAsAccordion) {
                $row.siblings().each(function () {
                    self.closeChildTable($(this));
                });
            }

            self.closeChildTable($row, function () {
                var $childRowColumn = self.getChildRow($row).find('td');
                $childRowColumn.empty();
                var $childTable = $('<div class="jtable-child-table-container"></div>').appendTo($childRowColumn);
                $childRowColumn.data('childTable', $childTable);
                $childTable.jtable(tableOptions);
                self.openChildRow($row);
                $childTable.hide().slideDown('fast', function () {
                    if (opened) {
                        opened({ childTable: $childTable });
                    }
                });
            });
        },

        closeChildTable: function ($row, closed) {
            var self = this;

            var $childRowColumn = this.getChildRow($row).find('td');
            var $childTable = $childRowColumn.data('childTable');
            if (!$childTable) {
                if (closed) {
                    closed();
                }
                return;
            }

            $childRowColumn.data('childTable', null);
            $childTable.slideUp('fast', function () {
                $childTable.jtable('destroy');
                $childTable.remove();
                self.closeChildRow($row);
                if (closed) {
                    closed();
                }
            });
        },

        isChildRowOpen: function ($row) {
            return (this.getChildRow($row).is(':visible'));
        },

        getChildRow: function ($row) {
            return $row.data('childRow') || this._createChildRow($row);
        },

        openChildRow: function ($row) {
            var $childRow = this.getChildRow($row);
            if (!$childRow.is(':visible')) {
                $childRow.show();
            }

            return $childRow;
        },

        closeChildRow: function ($row) {
            var $childRow = this.getChildRow($row);
            if ($childRow.is(':visible')) {
                $childRow.hide();
            }
        },

        /************************************************************************
        * OVERRIDED METHODS                                                     *
        *************************************************************************/

        _removeRowsFromTable: function ($rows, reason) {
            var self = this;

            if (reason == 'deleted') {
                $rows.each(function () {
                    var $row = $(this);
                    var $childRow = $row.data('childRow');
                    if ($childRow) {
                        self.closeChildTable($row);
                        $childRow.remove();
                    }
                });
            }

            base._removeRowsFromTable.apply(this, arguments);
        },

        /************************************************************************
        * PRIVATE METHODS                                                       *
        *************************************************************************/

        _createChildRow: function ($row) {
            var index = this._findRowIndex($row);
            var totalColumnCount = this._$table.find('thead th').length;
            var $childRow = $('<tr class="jtable-child-row"></tr>').append('<td colspan="' + totalColumnCount + '">heyo</td>');
            this._addRowToTable($childRow, index + 1);
            $row.data('childRow', $childRow);
            $childRow.hide();
            return $childRow;
        }
        
    });

})(jQuery);
